using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly;
using Polly.Retry;
using Quant.Entities;

namespace Quant.Services
{
    public class BinanceService
    {
        private readonly HttpClient _client;
        private readonly string _apiKey;
        private readonly string _apiSecret;

        /// <summary>
        /// 初始化Binance Futures API客户端
        /// </summary>
        /// <param name="apiKey">API密钥</param>
        /// <param name="apiSecret">API私钥</param>
        public BinanceService(IConfiguration config)
        {
            var environment = config["Environment"];
            var baseUrl = environment == "Test"
                ? "https://testnet.binancefuture.com"
                : "https://fapi.binance.com";

            _client = new HttpClient
            {
                BaseAddress = new Uri(baseUrl)
            };

            var apiKey = environment == "Test"
                ? config["Binance:Test:ApiKey"]
                : config["Binance:Production:ApiKey"];
            var apiSecret = environment == "Test"
                ? config["Binance:Test:ApiSecret"]
                : config["Binance:Production:ApiSecret"];
            _client.DefaultRequestHeaders.Add("X-MBX-APIKEY", apiKey);
            _apiKey = apiKey;
            _apiSecret = apiSecret;
        }

        /// <summary>
        /// 获取指定交易对的当前价格
        /// </summary>
        /// <param name="symbol">交易对符号 例：BTCUSDT</param>
        /// <returns>当前价格</returns>
        public async Task<decimal> GetCurrentPriceAsync(string symbol)
        {
            const int maxRetries = 3;
            int retryCount = 0;

            while (retryCount < maxRetries)
            {
                try
                {
                    var response = await _client.GetAsync($"fapi/v1/ticker/price?symbol={symbol}");
                    response.EnsureSuccessStatusCode();

                    var content = await response.Content.ReadAsStringAsync();
                    var result = JsonConvert.DeserializeObject<dynamic>(content);
                    return decimal.Parse(result.price.ToString());
                }
                catch (Exception ex) when (retryCount < maxRetries - 1)
                {
                    retryCount++;
                    await Task.Delay(1000 * retryCount);
                }
            }

            throw new Exception($"Failed to get price for {symbol} after {maxRetries} retries");
        }

        /// <summary>
        /// 获取指定交易对当前周期的成交量（基于K线数据）
        /// </summary>
        /// <param name="symbol">交易对符号 例：BTCUSDT</param>
        /// <param name="interval">K线周期（1m,5m,15m,1h）</param>
        /// <returns>最新K线的成交量值</returns>
        public async Task<decimal> GetCurrentVolumeAsync(string symbol, string interval)
        {
            var endpoint = $"fapi/v1/klines?symbol={symbol}&interval={interval}&limit=1";
            var response = await _client.GetAsync(endpoint);
            response.EnsureSuccessStatusCode();
            var klinesData = await response.Content.ReadFromJsonAsync<object[][]>();
            return decimal.Parse(klinesData[0][5].ToString());
        }

        /// <summary>
        /// 获取指定交易对历史周期的成交量（基于K线数据）
        /// </summary>
        /// <param name="symbol">交易对符号 例：BTCUSDT</param>
        /// <param name="interval">K线周期（1m,5m,15m,1h）</param>
        /// <param name="startTime">开始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <returns>历史K线的成交量值</returns>
        public async Task<decimal> GetCurrentVolumeAsync(string symbol, string interval, DateTime startTime, DateTime endTime)
        {
            var klines = new List<KlineData>();
            long startTimeParam = ((DateTimeOffset)startTime).ToUnixTimeMilliseconds();
            long endTimeParam = ((DateTimeOffset)endTime).ToUnixTimeMilliseconds();

            while (startTimeParam < endTimeParam)
            {
                var limit = Math.Min(1000, (int)((endTimeParam - startTimeParam) / (GetIntervalMilliseconds(interval)) + 1));
                var query = $"symbol={symbol}&interval={interval}&startTime={startTimeParam}&endTime={endTimeParam}&limit={limit}";
                var signature = GenerateSignature(query);

                var fullEndpoint = $"/fapi/v1/klines?{query}&signature={signature}";

                var _response = await _client.GetAsync(fullEndpoint);
                _response.EnsureSuccessStatusCode();

                var data = await _response.Content.ReadFromJsonAsync<object[][]>();
                klines.AddRange(data.Select(ConvertToKlineData));

                startTimeParam = (long)data.Last()[0] + 1;
            }

            var endpoint = $"/fapi/v1/klines?symbol={symbol}&interval={interval}&limit=1";
            var response = await _client.GetAsync(endpoint);
            response.EnsureSuccessStatusCode();
            var klinesData = await response.Content.ReadFromJsonAsync<object[][]>();
            decimal val = 0m;
            try
            {
                val = decimal.Parse(klinesData[0][5].ToString());
            }
            catch (Exception)
            {

            }
            return val;
        }

        /// <summary>
        /// 生成API请求签名（HMAC SHA256）
        /// </summary>
        /// <param name="queryString">请求参数字符串</param>
        /// <returns>16进制小写签名</returns>
        private string GenerateSignature(string queryString)
        {
            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_apiSecret)))
            {
                var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(queryString.ToString()));
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            }
        }

        /// <summary>
        /// 下达市价单
        /// </summary>
        /// <param name="config">策略配置参数</param>
        /// <param name="price">参考价格（用于计算数量）</param>
        /// <param name="direction">交易方向 BUY/SELL</param>
        /// <remarks>
        /// 风险控制：
        /// 1. 订单模式1使用固定金额，模式2使用仓位比例
        /// 2. 自动计算杠杆倍数下的实际可开仓位
        /// 3. 数量精度强制保留8位小数
        /// </remarks>
        public async Task PlaceOrderAsync(StrategyConfig config, decimal price, string direction)
        {
            var quantity = config.OrderMode == 1
                ? config.FixedAmount / price
                : config.PositionRatio * await GetAccountBalance();

            var parameters = new Dictionary<string, string>
            {
                { "symbol", config.Symbol },
                { "side", direction },
                { "type", "MARKET" },
                { "quantity", quantity.ToString("F8") },
                { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() }
            };

            var query = string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"));
            var signature = GenerateSignature(query);

            var request = new HttpRequestMessage(HttpMethod.Post, "/fapi/v1/order")
            {
                Content = new FormUrlEncodedContent(parameters.Concat(new[] { new KeyValuePair<string, string>("signature", signature) }))
            };

            var response = await _client.SendAsync(request);
            response.EnsureSuccessStatusCode();
        }

        /// <summary>
        /// 获取账户可用余额（仅限Futures账户）
        /// </summary>
        /// <returns>可用保证金余额</returns>
        /// <remarks>
        /// 查询范围说明：
        /// 1. 仅返回可用余额（不包括已冻结保证金）
        /// 2. 余额单位为USDT
        /// 3. 不包含持仓盈亏
        /// </remarks>
        public async Task<decimal> GetAccountBalance()
        {
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
            var query = $"timestamp={timestamp}";
            var signature = GenerateSignature(query);

            var endpoint = $"/fapi/v2/account?{query}&signature={signature}";

            var response = await _client.GetAsync(endpoint);
            response.EnsureSuccessStatusCode();

            var accountInfo = await response.Content.ReadFromJsonAsync<JObject>();
            if (accountInfo?["availableBalance"] == null)
                throw new InvalidOperationException("Invalid account balance response");
            return accountInfo["availableBalance"].Value<decimal>();
        }

        /// <summary>
        /// 转换K线间隔为毫秒数
        /// </summary>
        /// <param name="interval">K线间隔字符串</param>
        /// <returns>对应的毫秒数</returns>
        /// <exception cref="ArgumentException">不支持的间隔参数时抛出</exception>
        private static int GetIntervalMilliseconds(string interval)
        {
            return interval switch
            {
                "1m" => (int)TimeSpan.FromMinutes(1).TotalMilliseconds,
                "5m" => (int)TimeSpan.FromMinutes(5).TotalMilliseconds,
                "15m" => (int)TimeSpan.FromMinutes(15).TotalMilliseconds,
                "1h" => (int)TimeSpan.FromHours(1).TotalMilliseconds,
                "1d" => (int)TimeSpan.FromDays(1).TotalMilliseconds,
                _ => throw new ArgumentException("Unsupported interval")
            };
        }

        /// <summary>
        /// 转换原始K线数据为结构化对象
        /// </summary>
        /// <param name="kline">Binance API返回的原始数组</param>
        /// <returns>标准化K线数据对象</returns>
        private static KlineData ConvertToKlineData(object[] kline)
        {
            return new KlineData
            {
                OpenTime = DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(kline[0].ToString())).DateTime,
                OpenPrice = decimal.Parse(kline[1].ToString()),
                HighPrice = decimal.Parse(kline[2].ToString()),
                LowPrice = decimal.Parse(kline[3].ToString()),
                ClosePrice = decimal.Parse(kline[4].ToString()),
                Volume = decimal.Parse(kline[5].ToString()),
                CloseTime = DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(kline[6].ToString())).DateTime,
                QuoteVolume = decimal.Parse(kline[7].ToString()),
                TradeCount = int.Parse(kline[8].ToString()),
                TakerBuyBaseVolume = decimal.Parse(kline[9].ToString()),
                TakerBuyQuoteVolume = decimal.Parse(kline[10].ToString())
            };
        }

        private static readonly AsyncRetryPolicy _retryPolicy = Policy
            .Handle<HttpRequestException>()
            .Or<TaskCanceledException>()
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

        /// <summary>
        /// 获取历史K线数据（带重试机制）
        /// </summary>
        /// <param name="symbol">交易对</param>
        /// <param name="interval">K线间隔</param>
        /// <param name="startTime">起始时间（UTC）</param>
        /// <param name="endTime">结束时间（UTC）</param>
        /// <returns>历史K线数据集合</returns>
        /// <remarks>
        /// 特性说明：
        /// 1. 自动分页获取全部历史数据
        /// 2. 内置3次指数退避重试策略
        /// 3. 时间范围处理精度到毫秒级
        /// </remarks>
        public async Task<List<KlineData>> GetHistoricalKlinesAsync(string symbol, string interval, DateTime startTime, DateTime endTime)
        {
            return await _retryPolicy.ExecuteAsync(async () =>
            {
                var klines = new List<KlineData>();
                long startTimeParam = ((DateTimeOffset)startTime).ToUnixTimeMilliseconds();
                long endTimeParam = ((DateTimeOffset)endTime).ToUnixTimeMilliseconds();

                while (startTimeParam < endTimeParam)
                {
                    var limit = Math.Min(1000, (int)((endTimeParam - startTimeParam) / (GetIntervalMilliseconds(interval)) + 1));
                    var query = $"symbol={symbol}&interval={interval}&startTime={startTimeParam}&endTime={endTimeParam}&limit={limit}";
                    var signature = GenerateSignature(query);

                    var fullEndpoint = $"/fapi/v1/klines?{query}&signature={signature}";

                    var response = await _client.GetAsync(fullEndpoint);
                    response.EnsureSuccessStatusCode();

                    var data = await response.Content.ReadFromJsonAsync<object[][]>();
                    klines.AddRange(data.Select(ConvertToKlineData));

                    startTimeParam = Convert.ToInt64(data.Last()[0].ToString()) + 1;
                }

                return klines;
            });
        }
    }
}